/*
 * util.h - cve-check-tool
 *
 * Copyright (C) 2015 Intel Corporation
 *
 * cve-check-tool is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#pragma once

#define _GNU_SOURCE

#include <gio/gio.h>
#include <glib.h>
#include <libgen.h>
#include <libxml/xmlreader.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#include "cve-check-tool.h"
#include "cve-string.h"
#include "hashmap.h"

/**
 * Determine if a given file is actually a list of packages..
 */
bool is_package_list(cve_string *path);

/**
 * Determine if a file is one the enumerator is interested in
 *
 * @note Paths are relative basename, not absolute
 */
typedef bool (*package_match_func)(const gchar *);

/**
 * Recursively replace macros within a string
 *
 * @note Original string will be free'd, only perform on allocated
 * strings!
 *
 * @param macros Populated macro table (key->value)
 * @param str String to perform replacement on
 *
 * @return newly allocated string after replacements are performed
 */
gchar *demacro(CveHashmap *macros, gchar *str);

/**
 * Convert an XML formatted date into unix seconds
 *
 * @param date XML input
 * @return int64_t unix timestamp, or -1 if it doesn't parse
 */
int64_t parse_xml_date(const char *date);

/**
 * Search the repo source directory for all matching sources
 *
 * @param directory Base directory to recurse
 * @param match A function to determine "matching" source packages
 * @param recurse Whether we can recurse the given directory
 * @param cb A callback to execute when we encounter a matching package
 */
bool find_sources(const char *directory, package_match_func match, bool recurse, cve_add_callback cb);

/**
 * Implemented in a *similar* fashion to how g_autoptr is intended to
 * work in future, but without the concerns of MSVC, etc..
 */
#define DEF_AUTOFREE(N, C)                                                                                             \
        static inline void _autofree_func_##N(void *p)                                                                 \
        {                                                                                                              \
                if (p && *(N **)p) {                                                                                   \
                        /* To debug: printf("Freeing %s\n", #N); */                                                    \
                        C(*(N **)p);                                                                                   \
                        (*(void **)p) = NULL;                                                                          \
                }                                                                                                      \
        }

#define autofree(N) __attribute__((cleanup(_autofree_func_##N))) N

/**
 * Enable easier integration with autofree. Note this is still a single
 * pointer, you need to use it like: gstrv*, NOT gstrv.
 */
typedef gchar *gstrv;

/**
 * Autofree helper: Cleanup a GFileEnumerator
 *
 * @param enu A valid GFileEnumerator pointer
 */
static inline void cve_io_enum_close(GFileEnumerator *enu)
{
        if (!enu) {
                return;
        }
        g_file_enumerator_close(enu, NULL, NULL);
        g_object_unref(enu);
}

/**
 * We don't allow direct use, as this handles multiple GInputStream
 * subclasses.. */
static inline void cve_io_close(void *stream)
{
        if (!stream) {
                return;
        }
        g_input_stream_close(stream, NULL, NULL);
        g_object_unref(stream);
}
static inline void cve_io_close_out(void *stream)
{
        if (!stream) {
                return;
        }
        g_output_stream_close(stream, NULL, NULL);
        g_object_unref(stream);
}

DEF_AUTOFREE(GDateTime, g_date_time_unref)
DEF_AUTOFREE(GOptionContext, g_option_context_free)
DEF_AUTOFREE(gchar, g_free)
DEF_AUTOFREE(GError, g_error_free)
DEF_AUTOFREE(gstrv, g_strfreev)
DEF_AUTOFREE(GFileEnumerator, cve_io_enum_close)
DEF_AUTOFREE(GFile, g_object_unref)
DEF_AUTOFREE(GFileInputStream, cve_io_close)
DEF_AUTOFREE(GFileOutputStream, cve_io_close_out)
DEF_AUTOFREE(GOutputStream, cve_io_close_out)
DEF_AUTOFREE(GDataInputStream, cve_io_close)
DEF_AUTOFREE(GHashTable, g_hash_table_unref)
DEF_AUTOFREE(GTimeZone, g_time_zone_unref)
DEF_AUTOFREE(GZlibDecompressor, g_object_unref)
DEF_AUTOFREE(GKeyFile, g_key_file_unref)
DEF_AUTOFREE(cve_string, cve_string_free)
DEF_AUTOFREE(CveDB, cve_db_free)
DEF_AUTOFREE(char, free)
DEF_AUTOFREE(CveHashmap, cve_hashmap_free)

/**
 * Suger-utility: Determine if a string contains a certain word
 *
 * @param word String to check
 * @param needle "word" to search for
 *
 * @return a boolean value, true if the string contains the word
 */
static inline bool str_contains(const gchar *word, const gchar *needle)
{
        return strstr(word, needle) != NULL;
}

/**
 * Utility to replace one word with another in a string
 *
 * @note Only use on allocated strings, as this will free the original
 * string!
 *
 * @param source (Allocated) source string to perform replacement on
 * @param word Word to replace
 * @param replace Replacement word
 *
 * @return A newly allocated string with the replacement performed
 */
gchar *str_replace(gchar *source, const gchar *word, const gchar *replace);

/**
 * Determine if two null terminated strings are equal
 *
 * @param s1 String compare against s2
 * @param s2 String compare against s1
 * @return a boolean value, true if two strings are equal
 */
static inline bool streq(const char *s1, const char *s2)
{
        return !strcmp(s1, s2);
}

/**
 * Determine if the given path exists on disk
 *
 * @param p Path for the file to test
 * @return a boolean value, true if the condition is met
 */
bool cve_file_exists(const char *p);

/**
 * Determine if the given path exists, and is a directory
 *
 * @param p Path to the directory
 * @return a boolean value, true if the condition is met
 */
bool cve_is_dir(const char *p);

/**
 * Return the parent path for a given file
 *
 * @note This is an allocated string, and must be freed by the caller
 * @param p Path to file
 * @return a newly allocated string
 */
char *cve_get_file_parent(const char *p);

/**
 * Return absolute path to the dot file in format .basename(db_path).suffix
 *
 * @note This is an allocated string, and must be freed by the caller
 * @param db_path Path to the database file
 * @param suffix Suffix of the database file
 * @return a newly allocated string or NULL in case of error
 */
cve_string *make_db_dot_fname(const char *db_path, const char *suffix);

/**
 * Quick utility function to write small text files
 *
 * @note This will _always_ overwrite an existing file
 *
 * @param path Path of the file to be written
 * @param text Contents of the new file
 *
 * @return True if this succeeded
 */
bool cve_file_set_text(const char *path, char *text);

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 8
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=8 tabstop=8 expandtab:
 * :indentSize=8:tabSize=8:noTabs=true:
 */
